# -*- coding: utf-8 -*-
"""
Created on Mon Jan 22 22:45:02 2018

@author: Matt
"""

from math import log
import operator
import pickle
import treePlotter

# ！决策树工作原理：得到原始数据集，然后基于最好的属性划分数据集，由于特征值可能多于两个，
# 因此可能存在大于两个分支的数据集划分。划分后，数据被递归到分支的下一节点，再次进行划分
# 递归结束的条件：遍历完所有属性或者该分支下所有实例都属于相同分类（当然也可以设置最大分组数目）

# 计算给定数据集的香农熵
# 按照最后一列元素划分
def calcShannonEnt(dataSet):
    numEntries = len(dataSet)
    labelCounts = {}
    for featVec in dataSet:
        currentLabel = featVec[-1]
        if currentLabel not in labelCounts.keys():  # 为 yes 和 no
            labelCounts[currentLabel] = 0  # 初始化为0
        labelCounts[currentLabel] += 1
    shannonEnt = 0.0
    for key in labelCounts:
        prob = float(labelCounts[key]) / numEntries
        shannonEnt -= prob * log(prob, 2)
    return shannonEnt


def createDataSet():
    dataSet = [[1, 1, 'yes'],
               [1, 1, 'yes'],
               [1, 0, 'no'],
               [0, 1, 'no'],
               [0, 1, 'no']]
    labels = ['no surfacing', 'flippers']
    return dataSet, labels


# Test One
# print("Test One")
# myDataSet, labels = createDataSet()
# shannonEnt = calcShannonEnt(myDataSet)
# print("shannonEnt= %f" % shannonEnt)


#          待划分数据集、数据集特征(下标）、特征返回值
# 说人话：在待划分数据集里查找数据集特征下标位置的值
#        判断是否特征返回值，若是，去掉特征后的整体添加到新串中
def splitDataSet(dataSet, axis, value):
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            # 去掉下标为axis的特征数据 返回axis为value值的集合
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis + 1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet


# 选择最好的（熵最高的）数据集划分方式
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1
    # 原始香农熵，不做划分
    baseEntropy = calcShannonEnt(dataSet)
    bestInfoGain = 0.0
    bestFeature = -1
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        # 去重
        uniqueVals = set(featList)
        newEntropy = 0.0
        # 对每个特征划分数据集，计算新的熵值的和
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet) / float(len(dataSet))
            newEntropy += prob * calcShannonEnt(subDataSet)
        infoGain = baseEntropy - newEntropy
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature


# Test Two
# print("Test One")
# myDataSet, labels = createDataSet()
# bestFeature = chooseBestFeatureToSplit(myDataSet)


# 类似投票表决，返回出现次数最多的分类
def majorityCnt(classList):
    classCount = {}
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote] = 0
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]


# 创建树
def createTree(dataSet, labels):
    classList = [example[-1] for example in dataSet]
    # 当类别完全相同时停止继续划分
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    # 当遍历完所有特征时候返回出现次数最多的特征
    if len(dataSet[0]) == 1:
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)
    bestFeatLabel = labels[bestFeat]
    myTree = {bestFeatLabel: {}}  # todo
    del (labels[bestFeat])
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        subLabels = labels[:]
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
    return myTree


def retrieveTree(i):
    listOfTrees = [{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}},
                   {'no surfacing': {0: 'no', 1: {'flippers': {0: {'head': {0: 'no', 1: 'yes'}}, 1: 'no'}}}}
                   ]
    return listOfTrees[i]

# Test Three
# print("Test Three")
# myData,labels=createDataSet()
# myTree=createTree(myData,labels)


def classify(inputTree,featLabels,testVec):
    firstStr=list(inputTree.keys())[0]  # inputTree 为一字典or树 firstStr保存根节点
    secondDict=inputTree[firstStr]      # secondDict则是 firstStr这个key所对应的value
    featIndex=featLabels.index(firstStr)
    for key in secondDict.keys():
        if (testVec[featIndex]==key):
            if (type(secondDict[key]).__name__=='dict'):    # 如果对应的元素是判断节点
                classLabel=classify(secondDict[key],featLabels,testVec)
            else:
                classLabel=secondDict[key]
    return classLabel


# Test Four
# print("Test Four")
# myDat,labels=createDataSet()
# myTree=retrieveTree(0)
# classify(myTree,labels,[1,0])

# 将分类器存储在磁盘上 避免每次耗时进行决策树的生成
def storeTree(inputTree,filename):
    fw=open(filename,'wb')
    print(inputTree)
    pickle.dump(inputTree,fw)
    fw.close()

def grabTree(filename):
    fr=open(filename,'rb')
    return pickle.load(fr)

# Test Five
print("Test Five")
myDat,labels=createDataSet()
myTree=retrieveTree(0)
storeTree(myTree,'classifierStorage.txt')  # 使用二进制进行存储 直接打开txt显示乱码

def lenses():
    fr=open('lenses.txt')
    lenses=[inst.strip().split('\t')  for inst in fr.readlines()]
    lensesLabels=['age','prescript','astigmatic','tearRate']
    lensesTree=createTree(lenses,lensesLabels)
    treePlotter.createPlot(lensesTree)

#Test Six
print("Test Six")
lenses()


